/** * WorthDoing.ai * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: src/app/api/investigations/[id]/route.ts * Description: Investigation detail — state snapshot for the live UI (hypotheses, opportunities, budget, phase). */ import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { loadState } from "@/lib/agent/state"; import { resumeInvestigation, isRunning } from "@/lib/agent/runner"; export const dynamic = "force-dynamic"; export async function GET(_req: NextRequest, ctx: { params: Promise<{ id: string }> }) { const { id } = await ctx.params; if (!z.string().uuid().safeParse(id).success) { return NextResponse.json({ error: "Invalid investigation id." }, { status: 400 }); } try { const state = await loadState(id); // Self-heal: a "running" investigation with no live engine (server restarted) gets resumed. if (state.investigation.status === "running" && !isRunning(id)) { void resumeInvestigation(id); } const inv = state.investigation; return NextResponse.json({ investigation: { id: inv.id, objective: inv.objective, status: inv.status, phase: inv.phase, stopReason: inv.stopReason, outcome: inv.outcome, conclusion: inv.conclusion, budget: inv.budget, budgetUsed: inv.budgetUsed, model: inv.model, createdAt: inv.createdAt, startedAt: inv.startedAt, completedAt: inv.completedAt, error: inv.error, }, hypotheses: state.hypotheses, opportunities: state.opportunities.map((o) => ({ id: o.id, title: o.title, summary: o.summary, status: o.status, worthScore: o.worthScore, evidenceConfidence: o.evidenceConfidence, hasReport: !!o.reportMd, })), evidenceCount: state.evidence.length, searchCount: state.searches.length, sources: state.visitedSources, }); } catch { return NextResponse.json({ error: "Investigation not found." }, { status: 404 }); } }